package com.dhz.common.solr.server;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import org.apache.solr.client.solrj.SolrClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 服务于SolrClient的工具类，提供一些基出方法;
 *
 * @author Fyduan
 */
public class SolrClientUtils {

    private static final Logger logger = LoggerFactory.getLogger(SolrClientUtils.class);

    private SolrClientUtils(){}

    /**
     * 获得SolrClient类的ShortName - 名称
     * @param solrClient {@link SolrClient}
     * @return class short name
     */
    private static String getSolrClientTypeName(SolrClient solrClient) {
        Class<?> solrClientType = ClassUtils.isCglibProxy(solrClient) ? ClassUtils.getUserClass(solrClient) : solrClient.getClass();
        return ClassUtils.getShortName(solrClientType);
    }

    /**
     * 将core name拼接到SolrClient的baseUrl后面，生成指向core的访问地址
     * @param baseUrl solr服务的基本访问路径
     * @param core core name
     * @return coreUrl
     */
    public static String appendCoreToBaseUrl(String baseUrl, String core) {
        Assert.notNull(baseUrl);

        if (StringUtils.isEmpty(core)) {
            return baseUrl;
        }

        String url = baseUrl;
        if (!StringUtils.endsWith(baseUrl, "/")) {
            url = url + "/";
        }

        url = url + core;

        return url;
    }

    /**
     * 从传入的{@link SolrClient} 实例中复制出{@link HttpClient}
     * @param solrClient solr server 链接实例
     * @return {@link HttpClient} 复制的HttpClient
     */
    private static HttpClient readAndCloneHttpClient(SolrClient solrClient) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        // 取出HttpClient
        HttpClient sourceClient = readField(solrClient, "httpClient");

        // 复制副本并返回
        return cloneHttpClient(sourceClient);
    }

    /**
     * 复制一个HttpClient访问实例副本
     * @param sourceClient {@link HttpClient}
     * @return {@link HttpClient} 副本
     * @throws IllegalAccessException 抛出异常
     * @throws InvocationTargetException 抛出异常
     * @throws InstantiationException 抛出异常
     */
    private static HttpClient cloneHttpClient(HttpClient sourceClient) throws IllegalAccessException, InvocationTargetException, InstantiationException {
        if (sourceClient == null) {
            return null;
        }

        Class<?> clientType = ClassUtils.getUserClass(sourceClient);
        // 拿到构造器对象
        Constructor<?> constructor = ClassUtils.getConstructorIfAvailable(clientType, HttpParams.class);
        if (constructor != null) {
            HttpClient targetClient = (HttpClient) constructor.newInstance(sourceClient.getParams());
            BeanUtils.copyProperties(sourceClient, targetClient);
            return targetClient;
        } else {
            return new DefaultHttpClient(sourceClient.getParams());
        }
    }

    /**
     * 从{@link SolrClient} 中读取吃指定字段的值
     * @param solrClient {@link SolrClient}
     * @param fieldName 字段名称
     * @param <T> 返回类型
     * @return 返回字段内容
     */
    @SuppressWarnings("unchecked")
    private static <T> T readField(SolrClient solrClient, String fieldName) {
        Field field = ReflectionUtils.findField(solrClient.getClass(), fieldName);
        if (field == null) {
            return null;
        }

        // 设置字段可访问
        ReflectionUtils.makeAccessible(field);

        return (T) ReflectionUtils.getField(field, solrClient);
    }

    /**
     * Solr的属性名称并不是全部匹配getters/setters，该方法用于补充属性拷贝；
     * 检查所有的写入方法，并尝试找到其属性和值，然后写入值到该属性；
     * 忽略非写入方法的属性，忽略嵌套属性
     *
     * @param source 源对象
     * @param target 目标对象
     */
    private static void copyProperties(SolrClient source, SolrClient target) {
        BeanWrapperImpl wrapperImpl = new BeanWrapperImpl(source);
        for (PropertyDescriptor pd : wrapperImpl.getPropertyDescriptors()) {
            Method writer = pd.getWriteMethod();
            if (writer != null) {
                try {
                    Field property = ReflectionUtils.findField(source.getClass(), pd.getName());
                    if (property != null) {
                        ReflectionUtils.makeAccessible(property);
                        Object o = ReflectionUtils.getField(property, source);
                        if (o != null) {
                            writer.invoke(target, o);
                        }
                    }
                } catch (Exception e) {
                    logger.warn("Could not copy property value for: " + pd.getName(), e);
                }
            }
        }
    }

    /**
     * 根据传入的 {@link SolrClient} 生成一个针对core的副本，同时将client的链接url变更指向core
     * @param solrClient {@link SolrClient} 非空引用，新副本的属性来源
     * @param core 要指向的solr core 名称
     * @param <T> 传入的{@link SolrClient}对象
     * @return 返回副本 {@link org.apache.solr.client.solrj.impl.HttpSolrClient}
     *
     * @throws BeanInstantiationException 创建实例失败时抛出异常
     */
    @SuppressWarnings("unchecked")
    public static <T extends SolrClient> T clone(T solrClient, String core) {
        Assert.notNull(solrClient);

        String shortName = getSolrClientTypeName(solrClient);
        // 不能是实例化抽象类
        if (shortName.equals("SolrClient")) {
            return solrClient;
        }

        SolrClient cloneInstance = null;
        if (shortName.equals("HttpSolrClient")) {
            cloneInstance = cloneHttpSolrClient(solrClient, core);
        } else if (shortName.equals("LBHttpSolrClient")) {
            // cloneInstance = cloneLBHttpSolrClient(solrClient, core);
            logger.warn("暂不支持 [LBHttpSolrClient] 类型访问.");
        } else if (shortName.equals("CloudSolrClient")) {
            // cloneInstance = cloneCloudSolrClient(solrClient, core);
            logger.warn("暂不支持 [CloudSolrClient] 类型访问.");
        }

        // @throws {@link BeanInstantiationException} 创建实例失败时抛出异常
        if (cloneInstance == null) {
            throw new BeanInstantiationException(solrClient.getClass(), "Connot create instance of " + shortName + ".");
        }

        // 补充拷贝
        copyProperties(solrClient, cloneInstance);
        return (T) cloneInstance;

    }

    /**
     * 根据传入的 {@link SolrClient} 复制一个执行core的 新副本
     * @param solrClient {@link SolrClient}
     * @param core core name
     * @return 指向core的 {@link SolrClient}
     */
    private static SolrClient cloneHttpSolrClient(SolrClient solrClient, String core) {
        if (solrClient == null) {
            return null;
        }

        // 获得baseUrl属性的访问方法，用以获取baseUrl
        Method baseUrlGetterMethod = ClassUtils.getMethodIfAvailable(solrClient.getClass(), "getBaseURL");
        if (baseUrlGetterMethod == null) {
            return null;
        }

        // 根据baseUrl获得指向core的访问路径coreUrl
        String baseUrl = (String) ReflectionUtils.invokeMethod(baseUrlGetterMethod, solrClient);
        String coreUrl = appendCoreToBaseUrl(baseUrl, core);

        try {
            HttpClient client = readAndCloneHttpClient(solrClient);

            if (client != null) {
                Constructor<? extends SolrClient> constructor = ClassUtils.getConstructorIfAvailable(solrClient.getClass(), String.class, HttpClient.class);
                if (constructor != null) {
                    return BeanUtils.instantiateClass(constructor, coreUrl, client);
                }
            }

            Constructor<? extends SolrClient> constructor = ClassUtils.getConstructorIfAvailable(solrClient.getClass(), String.class);
            return BeanUtils.instantiateClass(constructor, coreUrl);
        } catch (Exception e) {
            throw new BeanInstantiationException(solrClient.getClass(), "Connot create instance of " + solrClient.getClass() + ".", e);
        }
    }

}
